<?php
/**
 * Srun3000客户端类
 *
 * @author sskaje sskaje@gmail.com
 * @version 2.2.0
 * @todo 提供一个通信的机制
 * @changelog:
 *		调整部分函数接口
 *		登陆增加写临时文件
 *		增加系统选项功能
 */
class sscSrun3000forBIT 
{
	/**
	 * 是否使用登陆产生的运行态数据
	 */
	const   SS_USE_RUNTIME_DATA      = 1;

	private $_syscfg = 1;

	private $username;
	private $password;
	private $drop = 1;
	private $client_type = 1;
	/**
	 * 客户端版本号
	 *
	 * @var int
	 */
	private $n = 1;
	
	private $url_host = '10.0.0.55';
	private $url_port = 3333;
	private $url_login     = 'http://%s:%d/cgi-bin/do_login';
	private $url_logout    = 'http://%s:%d/cgi-bin/do_logout';
	private $url_keepalive = 'http://%s:%d/cgi-bin/keeplive';
	
	private $drops = array(0=>'国际模式', 1=>'国内模式');
	private $client_types = array(
		1=>array('WEB 模式',      1, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)'), 
		2=>array('WIN客户端模式', 8, 'my session'),
		3=>array('Linux Client', 99, ''),
	);

	private $user_agent = '';

	private $_mac = '00:0a:42:cd:3c:0a';
	private $_key = '22222222';
	private $time_diff = 0;
	
	private $_os = 'WIN';
	
	private $uid = 0;
	
	public  $debug = 0;
	
	private $_init = 0;

	/**
	 * cookie文件存放路径
	 *
	 * @var string
	 */
	private $cookie_file = 'cookie.txt';
	/**
	 * 配置文件
	 * 
	 * @var string
	 */
	//private $_config_file = '~/.srun/config';
	private $_config_file = '/tmp/config';
	/**
	 * 配置文件
	 * 
	 * @var string
	 */
	private $_temp_file = '/tmp/.srun';
	/**
	 * cURL 资源
	 *
	 * @var cURL
	 */
	private $_curlres;

	public function __construct()
	{
	}

	/**
	 * 构造函数，默认参数构造
	 *
	 * @param string $username 用户名
	 * @param string $password 密码
	 */
	public function set_login($username, $password)
	{
		$this->username = $username;
		$this->password = $password;
	}
	/**
	 * 构造函数，默认参数构造
	 *
	 * @param string $host 主机IP
	 * @param int $port 主机端口
	 */
	public function set_host($host=NULL, $port=NULL)
	{
		if ($host != NULL)
			$this->url_host = $host;
		if ($port != NULL)
			$this->url_port = $port;
		
		$this->url_login     = sprintf($this->url_login,     $this->url_host, $this->url_port);
		$this->url_logout    = sprintf($this->url_logout,    $this->url_host, $this->url_port);
		$this->url_keepalive = sprintf($this->url_keepalive, $this->url_host, $this->url_port);
	}
	/**
	 * 设置客户端模式
	 *
	 * @param int $client_type 客户端模式，1->Web，2->客户端
	 */
	public function set_client_type($client_type)
	{
		if (isset($this->client_types[$client_type]))
		{
			$this->client_type = $client_type;
			$this->n           = $this->client_types[$client_type][1];
			$this->user_agent  = $this->client_types[$client_type][2];
		}
		else
		{
			throw new Exception('模拟客户端类型错误');
		}
	}
	/**
	 * 设置登陆模式
	 *
	 * @param int $drop 登陆模式，1->国内，0->国际
	 */
	public function drop($drop = 1)
	{
		if ($drop != 1 && $drop != 0)
		{
			throw new Exception('登录模式错误');
		}
		else if ($drop == 0)
		{
			echo "将以国际模式登陆\n";
		}
		else
		{
			echo "将以国内模式登陆\n";
		}

		$this->drop = $drop;
	}
	/**
	 * 设置cookie文件路径
	 *
	 * @param string $cookie_file
	 */
	public function set_cookie_fileloc($cookie_file)
	{
		$this->cookie_file = $cookie_file;
	}
	/**
	 * 初始化资源
	 *
	 */
	public function init()
	{
		$this->debug('Init: 初始化系统设置');
		
		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
		{
			$this->_os          = 'WIN';
			$this->_config_file = getenv('TEMP').'\.srunconfig';
			$this->_temp_file   = getenv('TEMP').'\.sruntemp';
		}
		else
		{
			$this->_os          = '';
			$this->_config_file = '/tmp/.srunconfig';
			#$this->_config_file = '~/.srun/config';
			$this->_temp_file   = '/tmp/.sruntemp';
		}

		$this->_curlres = curl_init();
		curl_setopt($this->_curlres, CURLOPT_VERBOSE, 0);
		curl_setopt($this->_curlres, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($this->_curlres, CURLOPT_COOKIEJAR, $this->cookie_file);

		curl_setopt($this->_curlres, CURLOPT_PORT, $this->url_port);
		
		if (($ua = $this->client_types[$this->client_type][2]))
		{
			curl_setopt($this->_curlres, CURLOPT_USERAGENT, $ua);
		}
		
		$this->_init = 1;
	}
	/**
	 * 登陆
	 *
	 */
	public function login($i=1)
	{
		$this->check_init();

		$this->generate_enc_key();
		if ($this->client_type == 2)
		{
			$password = $this->passencode($this->password);
		}
		else
		{
			$password = $this->password;
		}

		# 构造 登陆 Post串
		$login_str = 
			'username='  . urlencode($this->username) . 
			'&password=' . urlencode($password) . 
			'&drop='     . $this->drop . 
			'&type='     . $this->client_type . 
			'&n='        . $this->n . 
			'&mac='      . urlencode($this->passencode($this->_mac));

		$this->debug('Login: ' . $this->url_login);
		$this->debug('Login: ' . $login_str);

		curl_setopt($this->_curlres, CURLOPT_URL,        $this->url_login);
		curl_setopt($this->_curlres, CURLOPT_REFERER,    $this->url_login);
		curl_setopt($this->_curlres, CURLOPT_POST,       1);
		curl_setopt($this->_curlres, CURLOPT_POSTFIELDS, $login_str);
		$out = curl_exec($this->_curlres);

		$this->debug('Login response: ' . $out);

		#curl_setopt($this->_curlres, CURLOPT_COOKIE, $this->username.'%7C'.$this->password);
		$matches=array();
		
		$res = $this->parse_http_reponse($out);
		# WIN 客户端
		if ($this->client_type ==2 && preg_match('#^(\d+),\d+,\d+,\d+,\d+$#', $res['text'], $matches))
		{
			$this->uid = $matches[1];
			$this->write_runtimedata();
			return 1;
		}
		# WEB 客户端
		else if (
			$this->client_type == 1 && preg_match('#^\d+$#', $res['text'])
			)
		{
			$this->uid = $res['text'];
			$this->write_runtimedata();
			return;
		}
		# Linux客户端
		else if (
			$this->client_type == 3 && preg_match('#^(\d+)`$#', $res['text'], $matches)
			)
		{
			$this->uid = $matches[1];
			$this->write_runtimedata();
			return 1;
		}
		# 密码错误时间差校正
		else if (preg_match('#password_error@(\d+)#', $res['text'], $matches))
		{
			$server_time = intval($matches[1]);
			$this->time_diff = $server_time - time();
			# 二次登陆
			if ($i<2 && $this->login(++$i))
			{
				return 0;
			}
		}
		# 处理错误
		$this->login_error($res['text']);
	}
	/**
	 * 注销
	 *
	 */
	public function logout()
	{
		$this->check_init();
		$this->load_runtimedata();

		curl_setopt($this->_curlres, CURLOPT_URL, $this->url_logout);
		curl_setopt($this->_curlres, CURLOPT_POST, 1);
		$logout_str = 'uid='.$this->uid;

		$this->debug('Logout query: '.$logout_str);

		curl_setopt($this->_curlres, CURLOPT_POSTFIELDS, $logout_str);
		$out = curl_exec($this->_curlres);

		$this->debug('Logout: ' . $out);

		$res = $this->parse_http_reponse($out);

		if ($res['text'] == 'logout_ok')
		{
			echo '注销成功.';
		}
		else 
		{
			echo '注销失败';
		}
	}
	/**
	 * 保持连接
	 *
	 * @return string
	 */
	public function keepalive()
	{
		$this->check_init();

		$this->load_runtimedata();

		curl_setopt($this->_curlres, CURLOPT_URL, $this->url_keepalive);
		curl_setopt($this->_curlres, CURLOPT_POST, 1);
		$keepalive_str = 'uid='.$this->uid;
		
		$this->debug('Keepalive query: '.$keepalive_str);
		curl_setopt($this->_curlres, CURLOPT_POSTFIELDS, $keepalive_str);
		$out = curl_exec($this->_curlres);

		$this->debug('Keepalive: ' . $out);

		$res = $this->parse_http_reponse($out);

		if (preg_match('#^\d+,\d+,\d+$#', $res['text']))
		{
			return $res['text'];
		}
		else
		{
			//return $out;
			throw new Exception('系统已断开连接');
		}
	}

	/**
	 * 客户端模式密码加密
	 *
	 * @param string $pass
	 * @return string
	 */
	public function passencode($pass)
	{
		$pass = substr($pass, 0, 16);
		$len = strlen($pass);
		$ret = '';
		for ($i=0; $i<$len; $i++)
		{
			$_pass = ord($pass[$i]);

			$_key  = $this->_get_keychar($i);

			$_key  = $_key ^ $_pass;
			$ret .= $this->_build_key($_key, $i%2);
		}
		return $ret;
	}
	/**
	 * 生成密钥
	 *
	 */
	public function generate_enc_key()
	{
		$this->_key = strval(floor((time() + $this->time_diff)/60));
	}
	/**
	 * 算出每次加密所用的字符
	 *
	 */
	private function _get_keychar($num)
	{
		$len = strlen($this->_key);
		return ord($this->_key[ $len - $num % $len - 1 ]);
	}
	/**
	 * 加密字符
	 *
	 */
	private function _build_key($num, $reverse=0)
	{
		$ret = '';
		
		$_low = $num & 0x0f;
		
		$_high = $num >> 4;
		$_high = $_high & 0x0f;
		
		if (!$reverse)
		{
			$ret .= chr($_low  + 0x36);
			$ret .= chr($_high + 0x63);
		}
		else
		{
			$ret .= chr($_high + 0x63);
			$ret .= chr($_low  + 0x36);
		}
		return $ret;
	}
	/**
	 * 登陆提示
	 *
	 * @param string $out
	 */
	public function login_error($out)
	{
		if (strpos($out, 'password_error')!==false)
			throw new Exception("密码错误");

		switch($out)
		{
		case "user_tab_error":
			throw new Exception("认证程序未启动");
			break;
		
		case "username_error":
			throw new Exception("用户名错误");
			break;
		
		case "non_auth_error":
			echo "您无须认证，可直接上网";
			break;
		
		case "password_error":
			throw new Exception("密码错误");
			break;
		
		case "status_error":
			throw new Exception("用户已欠费，请尽快充值。");
			break;
		
		case "available_error":
			throw new Exception("用户已禁用");
			break;
		
		case "ip_exist_error":
			throw new Exception("用户已存在");
			break;
		
		case "usernum_error":
			throw new Exception("用户数已达上限");
			break;
		
		case "online_num_error":
			throw new Exception("该帐号的登录人数已超过限额\n如果怀疑帐号被盗用，请联系管理员。");
			break;
		
		case "mode_error":
			throw new Exception("系统已禁止WEB方式登录，请使用客户端");
			break;
		
		case "time_policy_error":
			throw new Exception("当前时段不允许连接");
			break;
		
		case "flux_error":
			throw new Exception("您的流量已超支");
			break;
		
		case "minutes_error":
			throw new Exception("您的时长已超支");
			break;
		
		case "ip_error":
			throw new Exception("您的IP地址不合法");
			break;
		
		case "mac_error":
			throw new Exception("您的MAC地址不合法");
			break;
			
		case 'version_error':
			throw new Exception('版本不对');
			break;
		
		default:
			throw new Exception("找不到认证服务器");
			break;
		}
	}
	/**
	 * debug数据
	 *
	 * @param string $message
	 */
	public function debug($message)
	{
		if ($this->debug)
			echo "\n=====================================\n[DEBUG] ", 
				date('Ymd H:i:s ') ,
				$message, 
				"\n=====================================\n";
	}
	/**
	 * 查询状态
	 *
	 */
	public function query()
	{
		$msg = $this->keepalive();
		$nums = explode(',', $msg);
		echo "
登陆用户   : {$this->username}
UID        : {$this->uid}
连接模式   : {$this->drops[$this->drop]}
客户端类型 : {$this->client_types[$this->client_type][0]}

流入数据   : {$nums[1]} bytes
流出数据   : {$nums[2]} bytes
连接时间   : {$nums[0]} seconds
";
	}
	
	/**
	 * 写配置文件
	 *
	 */
	public function write_config()
	{
		if (!file_exists(dirname($this->_config_file)))
			mkdir(dirname($this->_config_file), 0700, 1);

		if (!is_writable($this->_config_file) && !file_exists($this->_config_file))
		{
			throw new Exception('配置文件不可写: '.$this->_config_file);
		}
		
		$config = <<<CONFIG
[srun]
server_host={$this->url_host}
server_port={$this->url_port}
time_diff={$this->time_diff}
username=
password=
drop={$this->drop}
mac={$this->_mac}
client_type={$this->client_type}

CONFIG;
		file_put_contents($this->_config_file, $config);
	}
	
	/**
	 * 读配置文件
	 *
	 */
	public function load_config()
	{
		if (!is_readable($this->_config_file))
		{
			throw new Exception('配置文件不可读，请检查权限: '.$this->_config_file);
		}

		$configs = parse_ini_file($this->_config_file);

		$this->set_host($configs['server_host'], $configs['server_port']);

		$this->time_diff         = $configs['time_diff'];
		$this->drop              = $configs['drop'];
		$this->_mac              = $configs['mac'];
		$this->client_type       = $configs['client_type'];
		$this->username          = $configs['username'];
		$this->password          = $configs['password'];
	}

	/**
	 * 写临时文件
	 *
	 */
	public function write_runtimedata()
	{
		$this->debug('Write runtime data: '.$this->_temp_file);

		if (!file_exists(dirname($this->_temp_file)) && $this->_os != 'WIN')
		{
			$this->debug('Write runtime data: '.$this->_temp_file);
			mkdir(dirname($this->_temp_file), 0777, 1);
		}

		if (!is_writable($this->_temp_file) && 
			!file_exists($this->_temp_file) &&
			$this->_os != 'WIN' # WIN版 is_writable貌似有问题
			)
		{
			throw new Exception('临时文件不可写: '.$this->_temp_file);
		}

		# 将UID写入临时文件
		file_put_contents($this->_temp_file, $this->username. "\n".$this->uid);
	}
	/**
	 * 读临时文件
	 *
	 */
	public function load_runtimedata()
	{
		if (!$this->configured(self::SS_USE_RUNTIME_DATA))
			return 0;
		
		$this->debug('Load runtime data: '.$this->_temp_file);
		
		if (!is_readable($this->_temp_file))
		{
			throw new Exception('临时文件不可读，请检查权限: '.$this->_temp_file);
		}
		list($this->username, $this->uid) = explode("\n", file_get_contents($this->_temp_file));
	}
	/**
	 * 解析http响应
	 *
	 */
	public function parse_http_reponse($response)
	{
		# 如果客户端类型不为 Win版，直接构造
		if ($this->client_type != 2)
		{
			return array('code'=>NULL, 'text'=>$response);
		}
		else
		{
			$tmp = explode("\r\n", $response);

			if (isset($tmp[1]))
				return array('code'=>$tmp[0], 'text'=>$tmp[1], 'extra'=>(isset($tmp[2]) ? $tmp[2] : NULL));
			else
				return array('code'=>NULL, 'text'=>$tmp[0]);
		}
	}
	/**
	 * 检查系统是否初始化
	 */
	public function check_init()
	{
		if (!$this->_init)
			throw new Exception('请调用 sscSrun3000forBIT::init() 初始化设置');
	}
	/**
	 * 检查是否设置相关选项
	 */
	public function configured($config)
	{
		return $this->_syscfg & $config ? 1 : 0;
	}
	/**
	 * 设置系统选项
	 */
	public function syscfg($config)
	{
		$this->_syscfg = $config;
	}
}